package org.anderground.editor.gui.view.standard;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.KeyEventPostProcessor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Observable;

import javax.swing.FocusManager;
import javax.swing.JPanel;
import javax.swing.JTextField;

import org.anderground.core.model.Line;
import org.anderground.core.model.ModelData;
import org.anderground.core.model.Station;
import org.anderground.core.model.Transfer;
import org.anderground.core.model.Transition;
import org.anderground.core.model.Tunnel;
import org.anderground.core.util.Coord;
import org.anderground.core.util.Time;
import org.anderground.editor.edits.Action;
import org.anderground.editor.edits.AddDataAction;
import org.anderground.editor.edits.EditsManager;
import org.anderground.editor.edits.MoveStationLabelAction;
import org.anderground.editor.edits.MoveStationPositionAction;
import org.anderground.editor.edits.RemoveDataAction;
import org.anderground.editor.gui.properties.SelectionListener;
import org.anderground.editor.gui.view.ActiveMetroView;
import org.anderground.editor.gui.view.State;
import org.anderground.editor.gui.view.drawers.editor.EditorsDrawer;
import org.anderground.editor.gui.view.drawers.editor.StandardDrawer;
import org.anderground.editor.model.EditorModel;

/**
 * different triggers StandartView for listeners
 * 
 * @author Ilya Shurigyn (honorik@gmail.com)
 * @author Eldar Abusalimov (eldar.abusalimov@gmail.com)
 */
@SuppressWarnings("serial")
public class StandardView extends ActiveMetroView {

	private EditorsDrawer drawer;
	private List<Station> stationSelection = new LinkedList<Station>();
	private List<Tunnel> tunnelSelection = new LinkedList<Tunnel>();
	private List<SelectionListener> selectionListeners = new LinkedList<SelectionListener>();
	private final JPanel panel = this;
	private float minScale;

	public StandardView() {
		this.drawer = new StandardDrawer();
		this.setPreferredSize(new Dimension(500, 500));
		this.addMouseMotionListener(new MapScrollListener());
		this.addMouseListener(new AuxiliaryListener());
		this.addMouseListener(new AddStationListener());
		this.addMouseListener(new AddTunnelListener());
		this.addMouseMotionListener(new StationMoveListener());
		this.addMouseMotionListener(new LabelMoveListener());
		this.addMouseWheelListener(new MapScaleListener());
		this.addMouseListener(new StationsSelectionListener());
		this.addMouseListener(new TunnelSelectionListener());
		this.addMouseListener(new AddTransferListener());
		this.addMouseMotionListener(new AreaSelectionListener());
		this.addMouseListener(new AreaSelectionAluxListener());

		// TODO move in own class

		// remove station by Delete key listener using KeyboardFocus manager
		FocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(
				new KeyEventPostProcessor() {

					@Override
					public boolean postProcessKeyEvent(KeyEvent e) {

						// TODO m.b. this check is unnecessary
						if (JTextField.class.isInstance(e.getSource())) {
							return false;
						}
						if (e.getID() != KeyEvent.KEY_PRESSED) {
							return false;
						}
						if (e.getKeyCode() == KeyEvent.VK_DELETE) {

							Action action = new RemoveDataAction(
									getModelData(), stationSelection,
									tunnelSelection);
							getEditsManager().performAction(action);

							stationSelection = new LinkedList<Station>();
							tunnelSelection = new LinkedList<Tunnel>();
							selectionChanged();
						}
						return false;
					}

				});

	}

	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		drawer.setDrawerCenter(this.getWidth() / 2, this.getHeight() / 2);
		drawer.draw(stationSelection, tunnelSelection, g,
				ViewTriggers.showStationsLabels);
		if (areaStartPoint != null) {
			g.setColor(new Color(0, 0xAA, 0xFF, 0x33));
			int x = Math.min(areaStartPoint.x, areaEndPoint.x);
			int y = Math.min(areaStartPoint.y, areaEndPoint.y);
			int width = Math.abs(areaEndPoint.x - areaStartPoint.x);
			int height = Math.abs(areaEndPoint.y - areaStartPoint.y);

			g.fillRect(x, y, width, height);
			g.setColor(new Color(0, 0x00, 0x00));
			g.drawRect(x, y, width, height);
		}
	}

	private class StationMoveListener implements MouseMotionListener {

		// TODO local to the absolute coordinates transform

		@Override
		public void mouseDragged(MouseEvent arg0) {

			if (ViewTriggers.state != State.STATIONS_MOVE)
				return;
			int dx = arg0.getPoint().x - ViewTriggers.lastDragPoint.x;
			int dy = arg0.getPoint().y - ViewTriggers.lastDragPoint.y;

			moveSelectedStations(dx / drawer.getScale(), dy / drawer.getScale());
			ViewTriggers.lastDragPoint = new Point(arg0.getPoint().x, arg0
					.getPoint().y);

			repaint(); // repaintStationsAndTheirTransitions(selection);

		}

		@Override
		public void mouseMoved(MouseEvent arg0) {

		}

		private void moveSelectedStations(float dx, float dy) {
			Action action = new MoveStationPositionAction(dx, dy,
					stationSelection);
			getEditsManager().performAction(action);

		}

	}

	private class LabelMoveListener implements MouseMotionListener {

		// TODO local to the absolute coordinates transform

		@Override
		public void mouseDragged(MouseEvent arg0) {

			if (ViewTriggers.state != State.MOVE_LABEL)
				return;

			int dx = arg0.getPoint().x - ViewTriggers.lastDragPoint.x;
			int dy = arg0.getPoint().y - ViewTriggers.lastDragPoint.y;

			Station station = ViewTriggers.draggedLabelStation;
			if (station != null) {
				getEditsManager().performAction(
						new MoveStationLabelAction(dx, dy, station.getLabel()));

			}
			ViewTriggers.lastDragPoint = new Point(arg0.getPoint().x, arg0
					.getPoint().y);

			repaint(); // repaintStationsAndTheirTransitions(selection);

		}

		@Override
		public void mouseMoved(MouseEvent arg0) {

		}
	}

	private class MapScrollListener implements MouseMotionListener {
		public void mouseDragged(MouseEvent arg0) {
			// System.out.print();
			if ((ViewTriggers.state != State.SCROLL && arg0.getModifiers() != MouseEvent.BUTTON2_MASK)
					|| ViewTriggers.lastDragPoint == null) {
				return;
			}

			int dx = arg0.getPoint().x - ViewTriggers.lastDragPoint.x;
			int dy = arg0.getPoint().y - ViewTriggers.lastDragPoint.y;
			drawer.shift(dx, dy);
			ViewTriggers.lastDragPoint = new Point(arg0.getPoint().x, arg0
					.getPoint().y);

			panel.repaint();
		}

		public void mouseMoved(MouseEvent arg0) {

		}

	}

	private class AuxiliaryListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {

		}

		@Override
		public void mouseEntered(MouseEvent arg0) {

		}

		@Override
		public void mouseExited(MouseEvent arg0) {

		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			ViewTriggers.lastDragPoint = new Point(arg0.getPoint().x, arg0
					.getPoint().y);
			if (ViewTriggers.state == State.MOVE_LABEL)
				ViewTriggers.draggedLabelStation = drawer.getLabelsStation(arg0
						.getPoint());

		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			ViewTriggers.lastDragPoint = null;

		}
	}

	private class AddStationListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {
			if (arg0.getClickCount() == 2) {
				Action action = new AddDataAction(getModelData(), new Station(
						"", drawer.getAbsolutePoint(new Point(
								arg0.getPoint().x, arg0.getPoint().y)),
						new Coord(), new Time(), new Time(),
						new Station.Label()));
				getEditsManager().performAction(action);
			}

		}

		@Override
		public void mouseEntered(MouseEvent arg0) {

		}

		@Override
		public void mouseExited(MouseEvent arg0) {

		}

		@Override
		public void mousePressed(MouseEvent arg0) {

		}

		@Override
		public void mouseReleased(MouseEvent arg0) {

		}
	}

	private class AddTunnelListener implements MouseListener {

		private Station from = null;

		@Override
		public void mouseClicked(MouseEvent arg0) {
			if (ViewTriggers.state == State.ADD_TRANSITION) {
				if (from == null) {

					from = (!drawer.getStations(arg0.getPoint()).isEmpty()) ? drawer
							.getStations(arg0.getPoint()).get(0)
							: null;
					return;
				}
				Station to = (!drawer.getStations(arg0.getPoint()).isEmpty()) ? drawer
						.getStations(arg0.getPoint()).get(0)
						: null;
				if (from == to)
					return;
				if (to == null) {
					from = null;
					return;
				}
				if (!isDoubling(from, to, ViewTriggers.addTunelsLine)) {
					Action action = new AddDataAction(getModelData(),
							new Tunnel(from, to, 0, ViewTriggers.addTunelsLine,
									false));
					getEditsManager().performAction(action);
				}
				from = to;
			}
		}

		private boolean isDoubling(Station from, Station to, Line line) {
			for (Tunnel t : from.getTunnels()) {
				if (t.getToStation() == to && t.getLine() == line)
					return true;
			}
			return false;
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {

		}

		@Override
		public void mouseExited(MouseEvent arg0) {

		}

		@Override
		public void mousePressed(MouseEvent arg0) {

		}

		@Override
		public void mouseReleased(MouseEvent arg0) {

		}
	}

	private class AddTransferListener implements MouseListener {

		private Station from = null;

		@Override
		public void mouseClicked(MouseEvent arg0) {
			if (ViewTriggers.state == State.ADD_TRANSFER) {
				if (from == null) {

					from = (!drawer.getStations(arg0.getPoint()).isEmpty()) ? drawer
							.getStations(arg0.getPoint()).get(0)
							: null;
					return;
				}
				Station to = (!drawer.getStations(arg0.getPoint()).isEmpty()) ? drawer
						.getStations(arg0.getPoint()).get(0)
						: null;
				if (from == to)
					return;
				if (to == null) {
					from = null;
					return;
				}

				if (!isDoubling(from, to)) {
					Action action = new AddDataAction(getModelData(),
							new Transfer(from, to, 0));
					getEditsManager().performAction(action);
				}
				from = to;
			}
		}

		private boolean isDoubling(Station from, Station to) {
			for (Transfer t : from.getTransfers()) {
				if (t.getToStation() == to)
					return true;
			}
			return false;
		}

		@Override
		public void mouseEntered(MouseEvent arg0) {

		}

		@Override
		public void mouseExited(MouseEvent arg0) {

		}

		@Override
		public void mousePressed(MouseEvent arg0) {

		}

		@Override
		public void mouseReleased(MouseEvent arg0) {

		}
	}

	private class MapScaleListener implements MouseWheelListener {
		private final float SCALE_STEP = 1.1f;

		@Override
		public void mouseWheelMoved(MouseWheelEvent arg0) {
			float scaleShift = (arg0.getWheelRotation() < 0) ? SCALE_STEP
					: 1 / SCALE_STEP;
			float currScale = drawer.getScale();
			if (currScale * scaleShift > minScale)
				drawer.setScale(currScale * scaleShift);
			repaint();

		}

	}

	private Point areaStartPoint = null;
	private Point areaEndPoint = null;

	private class AreaSelectionListener implements MouseMotionListener {

		@Override
		public void mouseDragged(MouseEvent arg0) {
			if (areaStartPoint != null) {
				areaEndPoint = arg0.getPoint();
				repaint();
			}

		}

		@Override
		public void mouseMoved(MouseEvent arg0) {
			// TODO Auto-generated method stub

		}

	}

	private class AreaSelectionAluxListener implements MouseListener {

		@Override
		public void mouseClicked(MouseEvent arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public void mouseEntered(MouseEvent arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public void mouseExited(MouseEvent arg0) {
			// TODO Auto-generated method stub

		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			// Logger.i(arg0.getModifiers()+" " + (MouseEvent.BUTTON1_MASK |
			// MouseEvent.SHIFT_MASK));
			if (ViewTriggers.state == State.SELECTION
					&& (arg0.getModifiers() == MouseEvent.BUTTON1_MASK || arg0
							.getModifiers() == (MouseEvent.BUTTON1_MASK | MouseEvent.SHIFT_MASK)))
				areaStartPoint = arg0.getPoint();
			areaEndPoint = arg0.getPoint();

		}

		@Override
		public void mouseReleased(MouseEvent arg0) {
			if (areaStartPoint == null)
				return;
			selectStationsInArea(arg0.isShiftDown());
			areaStartPoint = null;
			repaint();

		}

		private void selectStationsInArea(boolean shiftPressed) {
			Coord startPoint = drawer.getAbsolutePoint(areaStartPoint);
			Coord endPoint = drawer.getAbsolutePoint(areaEndPoint);
			int x = Math.min(startPoint.getX(), endPoint.getX());
			int y = Math.min(startPoint.getY(), endPoint.getY());
			int width = Math.abs(endPoint.getX() - startPoint.getX());
			int height = Math.abs(endPoint.getY() - startPoint.getY());
			List<Station> toSelect = new ArrayList<Station>();
			for (Station s : EditorModel.getInstance().getData().getStations()) {
				Coord c = s.getPosition();
				if (c.getX() > x && c.getY() > y && c.getX() < x + width
						&& c.getY() < y + height)
					toSelect.add(s);
			}
			if (shiftPressed) {
				selectStations(toSelect);
			} else {
				stationSelection = toSelect;
			}
			repaint();

		}

	}

	private class TunnelSelectionListener extends MouseAdapter {
		@Override
		public void mouseClicked(MouseEvent arg0) {
			if (!ViewTriggers.tunnelSelectingEnabled)
				return;
			if (arg0.isShiftDown()) {
				for (Transition t : drawer.getTransitions(arg0.getPoint())) {
					if (tunnelSelection.contains(t))
						tunnelSelection.remove(t);
					else if (t instanceof Tunnel)
						tunnelSelection.add((Tunnel) t);
				}

			} else {
				tunnelSelection = new ArrayList<Tunnel>();
				for (Transition t : drawer.getTransitions(arg0.getPoint())) {
					if (t instanceof Tunnel)
						tunnelSelection.add((Tunnel) t);
				}
			}
			if (stationSelection != null) {
				/*
				 * repaintOnlyStations(oldSelection);
				 * repaintOnlyStations(stationSelection);
				 */
				repaintTunnels(tunnelSelection);
			}
			selectionChanged();
		}

		private void repaintTunnels(List<Tunnel> tunnelSelection) {
			// TODO
			repaint();
		}

	}

	private class StationsSelectionListener extends MouseAdapter {

		private boolean processClick;

		@Override
		public void mouseClicked(MouseEvent arg0) {
			if (!processClick)
				return;
			List<Station> pointStation = drawer.getStations(arg0.getPoint());
			if (arg0.isShiftDown()) {
				selectStations(pointStation);
			} else {
				stationSelection = drawer.getStations(arg0.getPoint());
			}
			selectionChanged();
		}

		@Override
		public void mousePressed(MouseEvent arg0) {
			processClick = true;
			if (!ViewTriggers.stationSelectingEnabled)
				return;
			List<Station> oldSelection = stationSelection;
			List<Station> pointStation = drawer.getStations(arg0.getPoint());
			if (arg0.isShiftDown()) {
				for (Station station : pointStation) {
					if (!stationSelection.contains(station)) {
						stationSelection.add(station);
						processClick = false;
					}

				}
			} else {
				if (!stationSelection.containsAll(pointStation))
					stationSelection = drawer.getStations(arg0.getPoint());
				// processClick = false;
			}
			if (stationSelection != null) {
				repaintOnlyStations(oldSelection);
				repaintOnlyStations(stationSelection);
			}
			selectionChanged();

		}
	}

	void repaintOnlyStations(List<Station> stations) {
		for (Station station : stations) {
			repaint(drawer.getRedrawRect(station));
		}
	}

	void repaintStationsAndTheirTransitions(List<Station> stations) {
		this.repaintOnlyStations(stations);
		for (Station station : stations) {
			for (Transition trans : this.getAllStationTransitions(station)) {
				repaint(drawer.getRedrawRect(trans));
			}
		}
	}

	private List<Transition> getAllStationTransitions(Station station) {
		List<Transition> trans = new LinkedList<Transition>();
		for (Transition t : getModelData().getTransfers()) {
			if (t.getFromStation() == station || t.getToStation() == station) {
				trans.add(t);
			}
		}
		for (Transition t : getModelData().getTunnels()) {
			if (t.getFromStation() == station || t.getToStation() == station) {
				trans.add(t);
			}
		}
		return trans;
	}

	@Override
	public void addSelectionListener(
			org.anderground.editor.gui.properties.SelectionListener listener) {
		this.selectionListeners.add(listener);
	}

	void selectionChanged() {
		for (org.anderground.editor.gui.properties.SelectionListener l : this.selectionListeners) {
			l.onSelectionChanged(stationSelection, tunnelSelection);
		}
	}

	@Override
	public void afterPanelInit() {

		try {
			drawer = drawer.getClass().newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		fitMap();
		this.minScale = drawer.getScale() * 0.01f;

	}

	private void fitMap() {
		Rectangle mapBounds = getMapBounds();
		drawer.setDrawerCenter(this.getWidth() / 2, this.getHeight() / 2);
		float scaleX = (float) (this.getHeight() / mapBounds.getHeight());
		float scaleY = (float) (this.getWidth() / mapBounds.getWidth());
		float finalScale = Math.min(scaleX, scaleY);
		drawer.setScale(finalScale);
		// drawer.setDrawerCenter(0, 0);
		drawer.shift(-(int) (mapBounds.getCenterX() * finalScale),
				-(int) (mapBounds.getCenterY() * finalScale));
	}

	@Override
	public void update(Observable o, Object arg) {
		this.repaint();
	}

	private void selectStations(List<Station> pointStation) {
		for (Station station : pointStation) {
			if (stationSelection.contains(station))
				stationSelection.remove(station);
			else
				stationSelection.add(station);
		}
	}

	private static ModelData getModelData() {
		return EditorModel.getInstance().getData();
	}

	private static EditsManager getEditsManager() {
		return EditsManager.getInstance();
	}

}
